/*************************************************************************
*File Name: ihttplib.cpp
*Author: 侯广臣
*2801059691@qq.com
*Created Time: Tue 19 Jan 2021 05:30:21 PM CST
 ************************************************************************/
#include "ihttplib.h"

//读取客户端发送文件，分析，保存信息
bool C_HttpReader::Run(void)
{
    int readLength;                 //每次读取信息长度
    CHECK_LINE_STATUS checkStatus;  //判断行是否完整状态
    bool requestStatus = false;     //读取是否完成
    while(1)
    {
        readLength = recv(m_sockfd, m_recvBuffer + m_readLength, m_recvBufferSize - m_readLength, 0);
        /*puts("####################################################");
        printf("recv 收到：\n%s\n", m_recvBuffer);
        puts("#####################################################");*/
        if(readLength <= 0)
        {
            requestStatus = false;
            break;
        }
        //统计读入多少字节
        m_readLength += readLength;
//这里可以加上如果接近m_recvBufferSize大小，动态扩充内存
        while((checkStatus = CheckLine()) == CHECK_LINE_OK)
        {
            //如果行完整，以字符串指针形式记录入m_httpContent中
            m_httpContent[m_lineCount++] = m_recvBuffer + m_checkIndex;
//这里可以加上如果接近m_httpContentSize大小，动态扩充内存
        }
        //判断是否因为行格式错误，还是行信息不完整跳出循环
        if(checkStatus == CHECK_LINE_BAD)
        {
            requestStatus = false;
            break;
        } //行信息不完整并且最后读取为字符为'\0'那么视为读取结束
        else if(m_recvBuffer[m_checkIndex - 1] == '\0')
        {
            requestStatus = true;
            break;
        }
    }
    if(requestStatus)
    {
        return ParseRequestLine(m_recvBuffer);
    }
    return false;
}
//检查行是否完整正确
CHECK_LINE_STATUS C_HttpReader::CheckLine(void)
{
    for(;m_checkIndex < m_readLength; m_checkIndex++)
    {
        //在\r后面找到\n
        if(m_recvBuffer[m_checkIndex] == '\r')
        {
            //数据不完整，继续读取数据
            if(m_checkIndex == m_readLength - 1)
            {
                return CHECK_LINE_INCOMPLETE;
            }
            //当前行完整，截断此行与此后数据
            if(m_recvBuffer[m_checkIndex+1] == '\n')
            {
                m_recvBuffer[m_checkIndex++] = '\0';
                m_recvBuffer[m_checkIndex++] = '\0';
                return CHECK_LINE_OK;
            }
            //\r后面没有\n视为发生格式错误
            return CHECK_LINE_BAD;
        }   //在\n前面找\r，原理同上
        else if(m_recvBuffer[m_checkIndex] == '\n')
        {
            if(m_checkIndex > 1 && m_recvBuffer[m_checkIndex - 1] == '\r')
            {
                m_recvBuffer[m_checkIndex - 1] = '\0';
                m_recvBuffer[m_checkIndex++] = '\0';
                return CHECK_LINE_OK;
            }
            return CHECK_LINE_BAD;
        }
    }
    //没有找到\n或者\r视为不是完整的行
    return CHECK_LINE_INCOMPLETE;
}
//解析请求行
bool C_HttpReader::ParseRequestLine(char *buffer)
{
    //在字符串中找到空格并用'\0'替代，分离出m_method:请求方法，URL，m_version:http版本
    m_method = buffer;
    if((m_url = strchr(m_method, ' ')) == nullptr)
    {
        return false;
    }
    *m_url++ = '\0';
    
    if((m_version = strchr(m_url, ' ')) == nullptr)
    {
        return false;
    }
    *m_version++ = '\0';

    return true;
}

//以下为C_HttpReader以外函数实现
//返回请求所代表的整数值，取值范围为 enum NETHOD 所包含类型
METHOD_NUMBER MethodNumber(const char *method)
{
	METHOD_NUMBER methodNumber = METHOD_BAD;
	switch(method[0])
	{
		case 'G':
			if(strcmp(method, "GET") == 0)
				methodNumber = METHOD_GET;
			break;
		case 'H':
			if(strcmp(method, "HEAD") == 0)
				methodNumber = METHOD_HEAD;
			break;
		case 'O':
			if(strcmp(method, "OPTIONS") == 0)
				methodNumber = METHOD_OPTIONS;
			break;
		case 'D':
			if(strcmp(method, "DELETE") == 0)
				methodNumber = METHOD_DELETE;
			break;
		case 'T':
			if(strcmp(method, "TRACE") == 0)
				methodNumber = METHOD_TRACE;
			break;
		case 'C':
			if(strcmp(method, "CONNECT") == 0)
				methodNumber = METHOD_CONNECT;
			break;
		case 'P':
			switch(method[1])
			{
				case 'O':
					if(strcmp(method, "POST") == 0)
						methodNumber = METHOD_POST;
					break;
				case 'U':
					if(strcmp(method, "PUT") == 0)
						methodNumber = METHOD_PUT;
					break;
				case 'A':
					if(strcmp(method, "METHOD_PATCH") == 0)
						methodNumber = METHOD_PATCH;
					break;
			}
			break;
		default: methodNumber = METHOD_BAD; break;
	}

	return methodNumber;
}

//如果解析后请求为错误，则调用标准请求错误，sockfd: 客户端套接字
//返回发送的字节数，如果错误返回 -1
int SendBadResponse(int sockfd)
{
	char stateLine[] = "HTTP/1.0 400 Bad Request\r\n";	//状态行
	char serverName[] = "Server: Linux Web Server\r\n";	//服务器名
	char contentType[] = "Content_Type: text/html\r\n\r\n";	//内容类型
	char content[] = 
		"<html><br><head><title>400 Bad Request</title></head><br><body><h1>Request Error!</h1></body></html>";
	//集中写结构体
	struct iovec iov[4];
	//初始化集中写结构体
	char* array[4] = {stateLine, serverName, contentType, content};
	for(int i = 0; i < 4; i++)
	{
		iov[i].iov_base = array[i];
		iov[i].iov_len = strlen(array[i]);
	}
	
	return writev(sockfd, iov, 4);
}

//解析正确后发送的响应头，sockfd: 客户端套接字 contentSzie: 响应体字节数 contentType: 响应体类型
//返回发送的字节数，如果错误返回 -1
int SendResponseHead(int sockfd, int contentSize, const char *contentType)
{
	char contentHead[256];
	int len = sprintf(contentHead,
	"CHTTP/1.0 200 OK\r\nServer: Linux Web Server\r\ncontent-Type: %s; charset=UTF-8\r\nContent-Length: %d\r\n\r\n",
			contentType, contentSize);

	return write(sockfd, contentHead, len);
}

//发送响应体，sockfd: 客户端套接字 fileName: 需要发送给客户端的文件
//返回发送的字节数，如果错误返回 -1
int SendResponseContent(int sockfd, const char *fileName)
{
	if(fileName == NULL	|| access(fileName, F_OK) != 0)
	{
		return -1;
	}

	int fd = open(fileName, O_RDONLY);
	if(fd == -1)
	{
		return -1;
	}

	struct stat fileStat;
	if(stat(fileName, &fileStat) != 0)
	{
		close(fd);
		return -1;
	}
	
	int sendLen = sendfile(sockfd, fd, NULL, fileStat.st_size);
	close(fd);

	return sendLen;
}


//发送Get请求的回应，错误反胃 -1
int SendGetResponse(int sockfd, const char *filePath, const char *url)
{
	if(sockfd < 0 || url == NULL)
	{
		return -1;
	}

    char fileName[256];
    if(strcmp(url, "/") == 0)
        StringCat(fileName, filePath, "/home.html");
    else
        StringCat(fileName, filePath, url);

	if(access(fileName, F_OK) != 0)
	{
		return SendBadResponse(sockfd);
	}
	int fd = open(fileName, O_RDONLY);
	if(fd == -1)
	{
		return -1;
	}

	struct stat fileStat;
	if(stat(fileName, &fileStat) == -1)
	{
		close(fd);
		return -1;
	}
    //发送响应头
	int sendHeadLen = SendResponseHead(sockfd, fileStat.st_size, "text/html");
	if(sendHeadLen <= 0)
	{
		close(fd);
		return -1;
	}
	//发送响应体
	int sendContentLen = SendResponseContent(sockfd, fileName);
	if(sendContentLen <= 0)
	{
		close(fd);
		return -1;
	}
	
	close(fd);
	return sendHeadLen + sendContentLen;
}

//把字符串str1, str2连接填充到catString中
//返回填充好的字符串，错误返回 NULL
char* StringCat(char catString[], const char *str1, const char *str2)
{
	if(strcpy(catString, str1) == NULL)
	{
		return NULL;
	}

	if(strcat(catString, str2) == NULL)
	{
		return NULL;
	}

	return catString;
}
